package com.zh.mycommon.util.crypto;

import com.zh.mycommon.value.Tuple2;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * 非对称加密
 * <p>
 * 扩展知识：
 * RSA密钥算法(非对称加密)：常用的非对称加密有 RSA、Elgamal、Rabin、D-H、ECC、SM2等
 */
public class RsaEncryptService {
    // RSA密钥算法(非对称加密)。常用的非对称加密有 RSA、Elgamal、Rabin、D-H、ECC、SM2等
    private static final String KEY_ALGORITHM = "RSA";
    //签名算法
    private static final String SIGN_ALGORITHM = "SHA256withRSA";

    private final String publicKey;
    private final String privateKey;


    public RsaEncryptService(String publicKey, String privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }

    /*
        test
     */
    public static void main(String[] args) throws Exception {
        String text = "hello world";

        var keyPair = generateRSAKey();
        RsaEncryptService service = new RsaEncryptService(keyPair.v1(), keyPair.v2());

        String signature = service.sign(text);
        System.out.println("signature = " + signature);
        System.out.println("isVerify ?：" + service.verify(text, signature));

        for (int i = 0; i < 3; i++) {
            System.out.println("--------------------------------------");

            String encryString = service.encrypt(text);
            System.out.println("encryptString = " + encryString);

            String plainString = service.decrypt(encryString);
            System.out.println("plainString = " + plainString);
        }


    }

    /**
     * 加密
     * 公钥加密
     *
     * @param text 明文文本，如果是空文本，则返回空字符串
     * @return 加密后的文本
     */
    public String encrypt(String text) {
        if (StringUtils.isEmpty(text)) return "";

        try {
            return doEncrypt(text, publicKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解密
     * 私钥解密
     *
     * @param encryptText 密文 如果是空文本，则返回空字符串
     * @return 解密后的文本
     */
    public String decrypt(String encryptText) {
        if (StringUtils.isEmpty(encryptText)) return "";

        try {
            return doDecrypt(encryptText, privateKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String doEncrypt(String data, String pubKeyStr) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.getDecoder().decode(pubKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        //RSA加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);

        return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    }


    private static String doDecrypt(String data, String priKeyStr) throws Exception {
        //base64编码的私钥
        byte[] decoded = Base64.getDecoder().decode(priKeyStr);

        PrivateKey priKey = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, priKey);

        //64位解码加密后的字符串
        byte[] inputByte = Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
        return new String(cipher.doFinal(inputByte), StandardCharsets.UTF_8);
    }


    /**
     * 签名
     * 私钥签名
     * @param text 明文文本
     * @return 加密后的文本
     */
    public String sign(String text) {
        if (StringUtils.isEmpty(text)) return "";

        try {
            return doSign(text, privateKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 验签
     * 公钥验签
     * @param text 密文
     * @return 解密后的文本
     */
    public boolean verify(String text, String signature) {
        if (StringUtils.isEmpty(text) || StringUtils.isEmpty(signature))
            return false;

        try {
            return doVerify(text, signature, publicKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String doSign(String text, String privateKey) throws Exception {
        Signature sign = Signature.getInstance(SIGN_ALGORITHM);
        //使用私钥初始化签名
        sign.initSign(stringToPrivKey(privateKey));

        sign.update(text.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    private static boolean doVerify(String text, String signature, String publicKey) throws Exception {
        Signature sign = Signature.getInstance(SIGN_ALGORITHM);

        //使用公钥初始化验证
        sign.initVerify(stringToPubKey(publicKey));
        sign.update(text.getBytes(StandardCharsets.UTF_8));

        byte[] bytes = Base64.getDecoder().decode(signature.getBytes(StandardCharsets.UTF_8));
        return sign.verify(bytes);
    }

    /**
     * 生成密钥
     *
     * @return 密钥<公钥, 私钥>
     * @throws NoSuchAlgorithmException 异常
     */
    public static Tuple2<String, String> generateRSAKey() throws Exception {
        KeyPairGenerator instance = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        instance.initialize(512, new SecureRandom());

        KeyPair keyPair = instance.genKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();

        String publicKeyStr = new String(Base64.getEncoder().encode(publicKey.getEncoded()), StandardCharsets.UTF_8);
        String privateKeyStr = new String(Base64.getEncoder().encode(privateKey.getEncoded()), StandardCharsets.UTF_8);

        return new Tuple2<>(publicKeyStr, privateKeyStr);
    }

    /**
     * 公钥字符串 转 PublicKey
     */
    public static PublicKey stringToPubKey(String encodedKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); //指定算法（例如RSA）
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 私钥字符串 转 PrivateKey
     * @param encodedKey 私钥字符串
     * @return PrivateKey 私钥
     */
    public static PrivateKey stringToPrivKey(String encodedKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); //指定算法（例如RSA）
        return keyFactory.generatePrivate(keySpec);
    }

}